import arrow
import ipaddress
from urllib import parse

from app.db.models.balances import Balances
from app.db.models.jobs import (
    Job,
    VulSettings,
    VulSettingCollect,
    VulAlert,
    SslSettings,
    SslSettingCollect,
    SecurityEventSettings,
    SecurityEventSettingCollect,
    ContentSettings,
    ContentSettingCollect,
    HttpSettings,
    HttpSettingCollect,
    HttpSettingAlert,
    PingSettings,
    PingSettingCollect,
    PingSettingAlert,
    ChangeCheckSettings,
    ChangeCheckSettingCollect
)
from app.db.models.asset_tasks import AssetTasks
from app.handler.v2_balances import BalancesOne
from app.api.v2.jobs import web_security_schedule_delete, available_schedule_delete
from app.libs.utils import get_default_area, enable_task_group, balance_task_count_sync
from app.libs.enums import WS_TASK_GROUP_TUPLE, AVAILABLE_TASK_GROUP_TUPLE, TaskType, CHANGE_CHECK_TYPE_TUPLE


class CreateTask(object):

    def __init__(self, user, target_url, settings, task_group_type, trigger_type):
        self.user = user
        self.target_url, self.source_ip, self.scan_url = self.parse_target_url(target_url)
        if not self.source_ip:
            self.job = Job.objects.filter(targetUrl=self.target_url, uid=user.id, verification__isVerified=True).first()
        else:
            self.job = Job.objects.filter(
                targetUrl=self.target_url, uid=user.id, sourceIp=self.source_ip, verification__isVerified=True).first()
        self.settings = settings
        self.task_group_type = task_group_type
        self.trigger_type = trigger_type
        self.balance = Balances.objects.filter(uid=user.id).first()

    @staticmethod
    def parse_target_url(source_target):
        tl = source_target.split("|")
        ip_str = ""
        scan_url = source_target
        if len(tl) >= 2:
            try:
                ipaddress.ip_address(tl[-1])
            except ValueError:
                pass
            else:
                scan_url = "|".join(tl[:-1])
                ip_str = tl[-1]
        url_obj = parse.urlparse(scan_url)
        target_url = f"{url_obj.scheme}://{url_obj.netloc}"
        return target_url, ip_str, scan_url

    @staticmethod
    def check_task_settings(task_settings):
        if not task_settings:
            return False
        if not isinstance(task_settings, list):
            return False
        if not all([isinstance(t, dict) for t in task_settings]):
            return False
        if {t.get("task_type", "") for t in task_settings} - set(WS_TASK_GROUP_TUPLE + AVAILABLE_TASK_GROUP_TUPLE):
            return False
        for t in task_settings:
            if i := t.get("interval"):
                try:
                    t["interval"] = int(i)
                except (TypeError, ValueError):
                    return False
        return True

    @staticmethod
    def check_balance_feature(balance, task_group_type):
        if not balance:
            return False
        if arrow.get(balance.planExpireTime).datetime < arrow.utcnow().datetime:
            return False
        if task_group_type == "web_security":
            return any([getattr(balance.feature, f"{t}Task", False) for t in WS_TASK_GROUP_TUPLE])
        else:
            return getattr(balance.feature, f"{task_group_type}Task", False)

    @staticmethod
    def get_web_security_default_config(user, trigger_type):
        # 漏洞检测默认配置
        vul_collect_settings = VulSettingCollect(
            type="increment" if trigger_type == "scheduleIncrement" else "full",
            depth=3,
            maxPage=1000,
            vul=[
                "1"
            ],
            includeUrl=[]
        )
        vul_settings = VulSettings(
            enable=False,
            enableSitePortraitTriggerMonitor=False,
            collect=vul_collect_settings,
            alert=VulAlert(),
            taskId=""
        )
        # ssl检测默认配置
        ssl_collect_settings = SslSettingCollect(
            pluginCategories=[
                "certificate",
                "protocol"
            ]
        )
        ssl_settings = SslSettings(
            enableSitePortraitTriggerMonitor=False,
            enable=False,
            collect=ssl_collect_settings,
            taskId=""
        )
        # 安全事件默认配置
        security_collect_settings = SecurityEventSettingCollect(
            plugins=[
                "black_links",
                "foreign_links",
                "broken_links",
                "cryjack",
                "malscan"
            ],
            depth=3,
            maxPage=1000,
            speed="high"
        )
        security_event_settings = SecurityEventSettings(
            enableSitePortraitTriggerMonitor=False,
            enable=False,
            collect=security_collect_settings,
            taskId=""
        )
        # 内容违规默认配置
        content_collect_settings = ContentSettingCollect(
            plugins=[
                "privacy_disclosure",
                "keyword",
            ],
            depth=3,
            maxPage=1000,
            includeUrl=[],
            customId=str(user.id),
            systemKeywordTypes=[1, 2, 3, 4, 5, 6, 7, 8, 9],
            customKeywordTypes=[1, 2, 3, 4, 5, 6, 7, 8, 9],
            privacyDisclosureTypes=[1, 2, 3, 4]
        )
        content_settings = ContentSettings(
            enableSitePortraitTriggerMonitor=False,
            enable=False,
            collect=content_collect_settings,
            taskId=""
        )
        # 篡改监测
        change_check_collect_settings = ChangeCheckSettingCollect(
            depth=3,
            maxPage=100,
            enableBaseChange=True,
            baseChangeCount=5,
            keywords=[],
            resources=[],
            changeType=list(CHANGE_CHECK_TYPE_TUPLE),
            type="full",
            includeUrl=[],
            excludeUrl=[],
        )
        change_check_settings = ChangeCheckSettings(
            enable=False,
            collect=change_check_collect_settings,
            taskId=""
        )
        data = {
            "vul": vul_settings,
            "ssl": ssl_settings,
            "securityEvent": security_event_settings,
            "content": content_settings,
            TaskType.change_check.value: change_check_settings,
        }
        return data

    def check_web_security_settings(self):
        task_types = {t.get("task_type"): t.get("interval", 86400) * 1000 for t in self.settings}
        if (len(task_types) == 1 and "ssl" in task_types) and not self.job.targetUrl.startswith("https"):
            return False, "http协议的资产不支持ssl安全监测"
        if not task_types:
            return False, "没有任务监测配置"
        if not self.job.targetUrl.startswith("https"):
            task_types.pop("ssl", 0)
        if self.trigger_type == "manual":
            task_types.pop(TaskType.change_check.value, 0)
        task_types = {t: v for t, v in task_types.items() if t in WS_TASK_GROUP_TUPLE}
        return True, task_types

    def get_web_security_settings(self):
        flag, task_types = self.check_web_security_settings()
        if not flag:
            return flag, task_types
        settings_map = self.get_web_security_default_config(self.user, self.trigger_type)
        result = {}
        if self.trigger_type != "manual" and AssetTasks.objects.filter(jobId=self.job.id, taskGroupType="web_security", triggerType="schedule").first():
            return False, f"{self.target_url} 已经存在周期web安全监测任务"
        for t, interval in task_types.items():
            setting_obj = settings_map.get(t)
            setting_obj.enable = getattr(self.balance.feature, f"{t}Task", False)
            if self.trigger_type != "manual":
                # 如果配置的频率快于套餐频率， 选择套餐频率
                setting_obj.interval = max(interval, getattr(self.balance.balance, f"{t}MaxFreq").total)
            result.update({f"{t}Settings": setting_obj})
        return True, result

    def get_available_interval(self):
        interval = 0
        if self.settings:
            interval = self.settings[0].get("interval") if self.settings[0].get("interval", None) else 900
        interval = interval * 1000
        return max(interval, getattr(self.balance.balance, f"{self.task_group_type}MaxFreq").total)

    @staticmethod
    def check_http_repeat_url(job, url):
        asset_tasks = AssetTasks.objects.filter(jobId=job.id, taskGroupType="http", triggerType="schedule")
        for asset_task in asset_tasks:
            exist_url = getattr(asset_task, "httpSettings").to_dict().get("target", "")
            if exist_url == url:
                return True
        return False

    def get_http_settings(self, name):
        interval = self.get_available_interval()
        if self.target_url == self.scan_url:
            url = self.job.targetUrl.rstrip('/') + '/'
        else:
            url = self.scan_url
        if self.check_http_repeat_url(self.job, url):
            return False, f"{url} 已经存在http监测任务"
        default_area = get_default_area(self.job.targetUrl, self.job.sourceIp)
        collect_settings = HttpSettingCollect(
            area=list(default_area["area"]),
            area_ipv6=list(default_area["area_ipv6"]),
            method="get",
            header={},
            redirect=3
        )
        alert_settings = HttpSettingAlert(
            respTime=12000,
            enableSmartMode=True,
            innerType="statusCodeRange",
            statusCodeRange=[
                [
                    "400",
                    "599"
                ]
            ],
            include=[],
            lossRate=50,
            nodeCount=5,
            alertCount=1,
            enable=True
        )
        http_settings = HttpSettings(
            enableSitePortraitTriggerMonitor=False,
            enable=True,
            name=name,
            sourceIp="",
            target=url,
            collect=collect_settings,
            alert=alert_settings,
            interval=interval,
            taskId=""
        )
        return True, {"httpSettings": http_settings}

    def get_ping_settings(self, name):
        interval = self.get_available_interval()
        if AssetTasks.objects.filter(jobId=self.job.id, taskGroupType="ping", triggerType="schedule").first():
            return False, f"{self.job.targetUrl} 已经存在ping监测任务"
        default_area = get_default_area(self.job.targetUrl, self.job.sourceIp)
        collect_settings = PingSettingCollect(
            area=list(default_area["area"]),
            area_ipv6=list(default_area["area_ipv6"])
        )
        alert_settings = PingSettingAlert(
            respTime=1000,
            lossRate=50,
            nodeCount=5,
            alertCount=1,
            enable=True
        )
        ping_settings = PingSettings(
            enableSitePortraitTriggerMonitor=False,
            enable=True,
            name=name,
            sourceIp="",
            target=self.job.targetUrl.rstrip('/') + '/',
            collect=collect_settings,
            alert=alert_settings,
            interval=interval,
            taskId=""
        )
        return True, {"pingSettings": ping_settings}

    def parse_task_settings(self, name):
        if self.trigger_type == "manual" and self.task_group_type != "web_security":
            return False, "只有web安全监测支持单次任务"
        if self.task_group_type == "web_security":
            return self.get_web_security_settings()
        elif self.task_group_type == "http":
            return self.get_http_settings(name)
        elif self.task_group_type == "ping":
            return self.get_ping_settings(name)
        return False, "仅支持http、 ping、web安全监测"

    def inc_manual_balance(self, type_map):
        for key, num in type_map.items():
            if num:
                BalancesOne(obj_balances=self.balance).inc_manual_task_used(key, num)

    def create_task(self, name):
        if not self.check_task_settings(self.settings):
            return False, "任务配置错误"
        if not self.check_balance_feature(self.balance, self.task_group_type):
            return False, "套餐失效"
        if not self.job:
            return False, "资产不存在"
        flag, task_settings = self.parse_task_settings(name)
        if not flag:
            return flag, task_settings
        asset_task_conf = {
            "name": name,
            "enableMonitor": True,
            "taskGroupType": self.task_group_type,
            "triggerType": "schedule" if self.trigger_type != "manual" else "manual",
            "targetUrl": self.target_url,
            "alertSettings": self.job.alertSettings,
            "isVerified": self.job.verification.isVerified,
            "jobId": self.job.id,
            "uid": self.job.uid,
            "createTime": arrow.utcnow().datetime,
            **task_settings
        }
        asset_task = AssetTasks(**asset_task_conf).save()
        delete_data = {"asset_task_ids": [asset_task.id, ]}
        try:
            enable_task_group(asset_task, insert=False, immediate_exec=True)
        except Exception:
            if self.task_group_type != "web_security":
                available_schedule_delete(delete_data)
            else:
                web_security_schedule_delete(delete_data)
            return False, "创建监测任务失败"
        # 单次web安全监测任务扣除套餐
        if self.task_group_type == "web_security" and self.trigger_type == "manual":
            _, task_types = self.check_web_security_settings()
            task_map = {t: 1 for t in task_types.keys()}
            try:
                self.inc_manual_balance(task_map)
            except Exception:
                web_security_schedule_delete(delete_data)
                return False, "创建监测任务失败"
        elif self.task_group_type in AVAILABLE_TASK_GROUP_TUPLE:
            balance_task_count_sync(self.user.id, self.balance)
        return True, self.job.targetUrl
